// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::env;
use std::fs;
use std::path::PathBuf;

use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
use cbindgen::Config;
use cbindgen::EnumConfig;
use cbindgen::Language;
use cbindgen::RenameRule;
use tempfile::TempDir;

static COPYRIGHT_CLAUSE: &str = "// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.";

static AUTOGENERATED_DISCLAIMER: &str =
    "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */";

static INCLUDE_GUARD: &str = "CROSVM_CONTROL_H_";

static CROSVM_CONTROL_HEADER_NAME: &str = "crosvm_control.h";

static REGISTERED_EVENTS_PROTO_FILENAME: &str = "registered_events.proto";
static REGISTERED_EVENTS_PROTO_SRC: &str = "../protos/src";

fn main() -> Result<()> {
    // Skip building dependencies when generating documents.
    if std::env::var("CARGO_DOC").is_ok() {
        return Ok(());
    }

    let proto_src =
        PathBuf::from(REGISTERED_EVENTS_PROTO_SRC).join(REGISTERED_EVENTS_PROTO_FILENAME);

    if !proto_src.exists() {
        bail!(
            "can't find {} in {}, won't be able to export for users",
            REGISTERED_EVENTS_PROTO_FILENAME,
            REGISTERED_EVENTS_PROTO_SRC
        );
    }

    let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();

    let output_dir = PathBuf::from(env::var("OUT_DIR").context("failed to get OUT_DIR")?);

    let proto_out = output_dir.join(REGISTERED_EVENTS_PROTO_FILENAME);

    fs::copy(proto_src, proto_out).context("couldn't copy proto to OUT_DIR")?;

    let output_file = output_dir
        .join(CROSVM_CONTROL_HEADER_NAME)
        .display()
        .to_string();

    let config = Config {
        language: Language::C,
        cpp_compat: true,
        header: Some(String::from(COPYRIGHT_CLAUSE)),
        include_guard: Some(String::from(INCLUDE_GUARD)),
        autogen_warning: Some(String::from(AUTOGENERATED_DISCLAIMER)),
        include_version: true,
        enumeration: EnumConfig {
            rename_variants: RenameRule::ScreamingSnakeCase,
            ..Default::default()
        },
        ..Default::default()
    };

    cbindgen::Builder::new()
        .with_crate(crate_dir)
        .with_config(config)
        .with_parse_deps(true)
        .with_parse_include(&["swap"])
        .generate()
        .context("Unable to generate bindings")?
        .write_to_file(output_file);

    // Do not perform the compilation check on Windows since GCC might not be installed.
    if std::env::var("CARGO_CFG_WINDOWS").is_ok() {
        return Ok(());
    }

    // Do a quick compile test of the generated header to ensure it is valid
    let temp_dir = TempDir::new()?;
    let test_file = temp_dir
        .path()
        .join("crosvm_control_test.c")
        .display()
        .to_string();

    fs::write(
        &test_file,
        format!("{}{}{}", "#include \"", CROSVM_CONTROL_HEADER_NAME, "\""),
    )
    .context("Failed to write crosvm_control test C file")?;

    cc::Build::new()
        .include(output_dir)
        .file(test_file)
        .compile("crosvm_control_test");

    // The above outputs cargo:rerun-if-env-changed directives, so we need to explicitly tell cargo
    // to rerun this script if anything in src/ is changed.
    println!("cargo:rerun-if-changed=src");
    Ok(())
}
